package com.appointment.pay.rest.service.impl;

import java.io.InputStream;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.appointment.pay.core.entity.WxpayRequest;
import com.appointment.pay.core.entity.WxPayTransactionHistory;
import com.appointment.pay.core.enums.TradeStateEnum;
import com.appointment.pay.core.mapper.WxPayTransactionHistoryMapper;
import com.appointment.pay.rest.enums.ErrEnum;
import com.appointment.pay.rest.exception.PayException;
import com.appointment.pay.rest.util.DateUtils;
import com.appointment.pay.rest.util.MD5Util;
import com.appointment.pay.rest.util.SignUtils;
import com.appointment.pay.rest.util.XMLUtil;
import com.appointment.pay.service.IWxpayService;
import com.appointment.pay.vo.WxpayRequestVo;
import com.appointment.pay.vo.WxpayResponseVo;
import com.appointment.pay.vo.WxpayTransactionVo;

/**
 * 微信支付服务实现
 * @author zhul
 *
 */
@Service
public class WxpayServiceImpl implements IWxpayService {
    
    public static String UTF8 = "UTF-8";
    
    public static String WX_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";//微信支付统一下单接口
    
    private static Logger logger = LoggerFactory.getLogger(WxpayServiceImpl.class);
    
    @Autowired
    @Value("${wx.pay.notify.url}")
    private String notify_url;
    
    @Autowired
    private WxPayTransactionHistoryMapper wxpayTransactionMapper;
    
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public WxpayResponseVo unifiedorder(WxpayRequestVo request) {
        logger.info("微信支付下单接口begin...");
        String appid = request.getAppid();
        if(StringUtils.isEmpty(appid)){
            throw new PayException(ErrEnum.ERR01.getMsg(),ErrEnum.ERR01.getCode());
        }
        String body = request.getBody();
        if(StringUtils.isEmpty(body)){
            throw new PayException(ErrEnum.ERR02.getMsg(),ErrEnum.ERR02.getCode());
        }
        String device_info = request.getDevice_info();
        if(StringUtils.isEmpty(device_info)){
            device_info = "WEB";//默认值
        }
        String mch_id = request.getMch_id();
        if(StringUtils.isEmpty(mch_id)){
            throw new PayException(ErrEnum.ERR03.getMsg(),ErrEnum.ERR03.getCode());
        }
        Integer total_fee = request.getTotal_fee();
        if(total_fee == null || total_fee.intValue() <= 0){
            throw new PayException(ErrEnum.ERR04.getMsg(),ErrEnum.ERR04.getCode());
        }
        String trade_type = request.getTrade_type();
        if(StringUtils.isEmpty(trade_type)){
            throw new PayException(ErrEnum.ERR05.getMsg(),ErrEnum.ERR05.getCode());
        }
        String key = request.getKey();
        if(StringUtils.isEmpty(key)){
            throw new PayException(ErrEnum.ERR06.getMsg(),ErrEnum.ERR06.getCode());
        }
        Integer userId = request.getUserId();
        if(userId == null || userId.intValue() <= 0){
            throw new PayException(ErrEnum.ERR08.getMsg(),ErrEnum.ERR08.getCode());
        }
        String openid = request.getOpenid();
        //将商户密钥存到redis，结果通知验签时要用
        redisTemplate.boundValueOps(mch_id).set(key);
        
        WxpayRequest payReq = new WxpayRequest();
        payReq.setAppid(appid);
        payReq.setBody(body);
        payReq.setDevice_info(device_info);
        payReq.setFee_type("CNY");
        payReq.setMch_id(mch_id);
        String out_trade_no = SignUtils.createOutTradeNo();
        String nonce_str = MD5Util.MD5Encode(out_trade_no, UTF8);
        payReq.setNonce_str(nonce_str);
        payReq.setNotify_url(notify_url);
        payReq.setOut_trade_no(out_trade_no);
        String ip = SignUtils.getLocalIP();
        logger.info("ip==="+ip);
        
        payReq.setSpbill_create_ip(StringUtils.isEmpty(ip) ? "127.0.0.1":ip);
        payReq.setTime_start(DateUtils.printNow());
        payReq.setTotal_fee(total_fee);
        payReq.setTrade_type(trade_type);
        if("JSAPI".equalsIgnoreCase(trade_type)){
            payReq.setOpenid(openid);
        }
        String sign = sign(payReq,key);
        payReq.setSign(sign);
        //保存下单数据
        saveTransaction(payReq,userId);
        
        String data = createXmlRequestStr(payReq);
        try{
        	//调用微信下单接口
        	HttpClient client = new DefaultHttpClient();  
            HttpPost post = new HttpPost(WX_UNIFIEDORDER);  
            StringEntity entity = new StringEntity(data,ContentType.create("application/xml", Consts.UTF_8));        
            post.setEntity(entity);  

            HttpResponse response = client.execute(post);  
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
                InputStream is = response.getEntity().getContent();  
                String result = XMLUtil.inStream2String(is);
                logger.info("微信支付下单接口返回信息==="+result);
                if(StringUtils.isNotEmpty(result)){
                	Map map = XMLUtil.doXMLParse(result);
                	if(map != null){
                	    if(map.containsKey("return_code")){
                	        //返回状态码
                	        String returnCode = map.get("return_code").toString();
                	        if("SUCCESS".equalsIgnoreCase(returnCode)){
                	            //业务结果
                	            String resultCode = map.get("result_code").toString();
                	            if("SUCCESS".equalsIgnoreCase(resultCode)){
                	                //微信生成的预支付回话标识，用于后续接口调用中使用，该值有效期为2小时
                	                String prepayId = map.get("prepay_id").toString();
                	                WxPayTransactionHistory transaction = new WxPayTransactionHistory();
                	                transaction.setOutTradeNo(out_trade_no);
                	                transaction.setPrepayId(prepayId);
                	                String code_url = null;
                	                if(map.containsKey("code_url")){
                	                    code_url = (String)map.get("code_url");//二维码
                	                    transaction.setCodeUrl(code_url);//二维码
                	                }
                	                
                	                wxpayTransactionMapper.updateByOutTradeNo(transaction);
                	                WxpayResponseVo vo = new WxpayResponseVo();
                	                vo.setPrepayid(prepayId);
                	                vo.setPkg("Sign=WXPay");
                	                if(StringUtils.isNotEmpty(code_url)){
                	                    vo.setCode_url(code_url);
                	                }
                	                return vo;
                	            }
                	        }
                	    }else{
                	        logger.error("微信支付下单接口返回return_code为空！！！");
                	    }
                	}
                }
                
            }  
        }catch(Exception ex){
        	logger.error("调用微信支付下单接口异常!",ex);
        	throw new PayException(ErrEnum.ERR07.getMsg(),ErrEnum.ERR07.getCode());
        }
        
        logger.info("微信支付下单接口end...");
        return null;
    }
    
    /**
     * 生成支付请求参数签名
     * @param payReq
     * @param key
     * @return
     */
    private String sign(WxpayRequest payReq,String key){
        SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
        parameters.put("appid", payReq.getAppid());  
        parameters.put("mch_id", payReq.getMch_id());  
        parameters.put("device_info", payReq.getDevice_info());  
        parameters.put("nonce_str", payReq.getNonce_str()); 
        parameters.put("body", payReq.getBody());
        parameters.put("fee_type", payReq.getFee_type());
        parameters.put("notify_url", payReq.getNotify_url());
        parameters.put("out_trade_no", payReq.getOut_trade_no());
        parameters.put("spbill_create_ip", payReq.getSpbill_create_ip());
        parameters.put("time_start", payReq.getTime_start());
        parameters.put("total_fee", payReq.getTotal_fee());
        parameters.put("trade_type", payReq.getTrade_type());
        if(payReq.getOpenid() != null){
            parameters.put("openid", payReq.getOpenid());
        }
        String sign = SignUtils.createSign(UTF8, parameters, key);
        logger.info("生成的签名sing===="+sign);
        return sign;
    }
    
    /**
     * 拼接请求xml字符串
     * @param payReq
     * @return
     */
    private String createXmlRequestStr(WxpayRequest payReq){
    	StringBuffer sb = new StringBuffer();
    	sb.append("<xml>");
    	sb.append("<appid>").append(payReq.getAppid()).append("</appid>");
    	sb.append("<mch_id>").append(payReq.getMch_id()).append("</mch_id>");
    	sb.append("<device_info>").append(payReq.getDevice_info()).append("</device_info>");
    	sb.append("<nonce_str>").append(payReq.getNonce_str()).append("</nonce_str>");
    	sb.append("<body>").append(payReq.getBody()).append("</body>");
    	sb.append("<fee_type>").append(payReq.getFee_type()).append("</fee_type>");
    	sb.append("<notify_url>").append(payReq.getNotify_url()).append("</notify_url>");
    	sb.append("<out_trade_no>").append(payReq.getOut_trade_no()).append("</out_trade_no>");
    	sb.append("<spbill_create_ip>").append(payReq.getSpbill_create_ip()).append("</spbill_create_ip>");
    	sb.append("<time_start>").append(payReq.getTime_start()).append("</time_start>");
    	sb.append("<total_fee>").append(payReq.getTotal_fee()).append("</total_fee>");
    	sb.append("<trade_type>").append(payReq.getTrade_type()).append("</trade_type>");
    	sb.append("<sign>").append(payReq.getSign()).append("</sign>");
    	if(payReq.getOpenid() != null){
    	    sb.append("<openid>").append(payReq.getOpenid()).append("</openid>");
    	}
    	sb.append("</xml>");
    	logger.info("微信支付下单请求参数===="+sb.toString());
    	return sb.toString();
    }
    
    /**
     * 保存下单记录
     */
    private void saveTransaction(WxpayRequest payReq,Integer userId){
        WxPayTransactionHistory transaction = new WxPayTransactionHistory();
        transaction.setAppId(payReq.getAppid());
        transaction.setBody(payReq.getBody());
        transaction.setDeviceInfo(payReq.getDevice_info());
        transaction.setFeeType(payReq.getFee_type());
        transaction.setMchId(payReq.getMch_id());
        transaction.setOutTradeNo(payReq.getOut_trade_no());
        transaction.setSpbillCreateIp(payReq.getSpbill_create_ip());
        transaction.setTimeStart(System.currentTimeMillis());
        transaction.setTotalFee(payReq.getTotal_fee());
        transaction.setTradeType(payReq.getTrade_type());
        transaction.setTradeState(TradeStateEnum.NOTPAY.getCode());
        transaction.setState(0);
        transaction.setStateName(TradeStateEnum.NOTPAY.getMessage());
        transaction.setCreateTime(new Date());
        transaction.setUserId(userId);
        transaction.setIsSubscribe("N");
        transaction.setOpenId(payReq.getOpenid());
        
        wxpayTransactionMapper.insertWxPayTransactionHistory(transaction);
    }

    @Override
    public WxpayTransactionVo queryOrder(String prepayid) {
        WxPayTransactionHistory tran = wxpayTransactionMapper.queryByPrepayid(prepayid);
        if(tran != null){
            WxpayTransactionVo vo = new WxpayTransactionVo();
            vo.setOutTradeNo(tran.getOutTradeNo());
            vo.setStateName(tran.getStateName());
            vo.setTimeEnd(tran.getTimeEnd());
            vo.setTotalFee(tran.getTotalFee());
            vo.setTradeState(tran.getTradeState());
            vo.setTransactionId(tran.getTransactionId());
            return vo;
        }
        return null;
    }
}
